#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <pcap.h>
#include <signal.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>

#include "nsgbot_capture.h"
#include "pseudo_pkt.h"

#ifdef DEBUG_MEM
long mem_alloc;
long mem_free;
long mem_alloc_tcp;
long mem_alloc_udp;
long mem_free_irc;
long fifo_count;
#endif

#define PROMISCUOUS 1
#define SIZE_OF_8021Q 4

/* argument options */
int cflag;
int enable_tcp = 1;
int enable_udp = 1;
#define tcpPipe "/tmp/nsgbot_tcpQ"
#define udpPipe "/tmp/nsgbot_udpQ"


char *device;    /* Network interface name (ex. eth0) */
static pcap_t *pd;
int tcp_fd, udp_fd;    /* file pointer of named pipe */

void analysis(u_char *user, const struct pcap_pkthdr *pkthdr, const u_char *packet)
{
  unsigned short ether_type;
  struct ether_header *ehdr;
  struct timeval timestamp;
  ehdr = (struct ether_header *)packet;
  ether_type = ntohs(ehdr->ether_type);

  /* struct */
  struct iphdr *iph;
  u_int8_t protocol;
  u_int16_t tot_len;
  u_char *ip_packet;
  u_char *upper;
  u_char *payload;
  
  pseudo_pkt *ppkt;
  struct tcphdr *tcphdr;
  struct udphdr *udphdr;
  //printf("Called analysis\n");
  
  /* ethertype */
  if (ether_type == ETH_P_IP)
	{
	  /* ethernet header + ip payload */
	  ip_packet = (u_char *)(packet + sizeof(struct ether_header));
	  //printf("ethernet (%d)\n", sizeof(struct ether_header));
	}
  else if (ether_type == ETH_P_8021Q)
	{
	  //printf("802.1Q (%d)\n", sizeof(struct ether_header)+SIZE_OF_8021Q);
	  ip_packet = (u_char *)(packet + sizeof(struct ether_header) + SIZE_OF_8021Q);
	}
  else
	{
	  //fprintf(stderr, "unknown ethernet header: %x\n", ether_type);
	  return;
	}

  /* decode IP Header */
  iph = (struct iphdr *)ip_packet;

  if ( (ppkt = (pseudo_pkt *)calloc(1,sizeof(pseudo_pkt)) ) == NULL) {
	fprintf(stderr, "Calloc error of pseudo_pkt");
	exit;
  }
  // timestamp
  gettimeofday(&timestamp, NULL);
  ppkt->tv_sec = timestamp.tv_sec;
  ppkt->tv_usec = timestamp.tv_usec;
  ppkt->saddr = iph->saddr;
  ppkt->daddr = iph->daddr;
  ppkt->protocol = iph->protocol;
  tot_len = ntohs(iph->tot_len);
  ppkt->packet_size = tot_len;

  
  upper = ip_packet + (iph->ihl * 4);
  switch(ppkt->protocol)
	{
	case IPPROTO_TCP:
	  tcphdr = (struct tcphdr *)upper;

	  /* Assign pseudo packet */
	  ppkt->sport = ntohs(tcphdr->source);
	  ppkt->dport = ntohs(tcphdr->dest);
	  payload = ip_packet + (iph->ihl * 4) + (tcphdr->doff * 4);
	  tot_len = tot_len - (iph->ihl * 4) - (tcphdr->doff * 4);


	  // copy payload
	  if (tot_len > SAMPLE_SIZE) tot_len = SAMPLE_SIZE;
	  memcpy(ppkt->payload, payload, tot_len);
	  ppkt->payload_size = tot_len;

	  
	  // Write nsgbot_tcpQ (FIFO)
	  write(tcp_fd, (void*)ppkt, sizeof(pseudo_pkt));
	  break;

	case IPPROTO_UDP:
	  udphdr = (struct udphdr *)upper;

	  /* Assign pseudo packet */
	  ppkt->sport = ntohs(udphdr->source);
	  ppkt->dport = ntohs(udphdr->dest);
	  payload = (ip_packet + (iph->ihl * 4) + 8);  /* 8 is UDP header size */
	  tot_len = tot_len - (iph->ihl * 4) - 8;


	  
	  //print_pkt(ppkt,0);
	  printf("%d\n",sizeof(pseudo_pkt));
	  // copy payload
	  if (tot_len > SAMPLE_SIZE) tot_len = SAMPLE_SIZE;
	  memcpy(ppkt->payload, payload,  tot_len);
	  ppkt->payload_size = tot_len;
	  
	  printf("payload size:%d\n",ppkt->payload_size);
	  
	  // Write nsgbot_udpQ (FIFO)
	  write(udp_fd, (void*)ppkt, sizeof(pseudo_pkt));
	  break;
	}
  free(ppkt);  // copied to each FIFO 
  return;
}

void init_pipe()
{
  int ret_val;

  PDEBUG("TEST data structure\n");
  printf("size of pseudo_pkt:%d\n", sizeof(pseudo_pkt));
  pseudo_pkt *pkt;
  pkt = (pseudo_pkt*)calloc(1, sizeof(pseudo_pkt));
  //printf("start point of pkt:%x\n", pkt);
  //printf("payload:%x\n", pkt->payload);
  printf("packet_size:%p\n", pkt->saddr);
  printf("payload_size:%x\n", pkt->payload_size);
  printf("tag:%x\n", pkt->tag);
  free(pkt);
  
  PDEBUG("Called init pipe\n");
  // open tcp FIFO
  if (enable_tcp == 1) {
	tcp_fd = open(tcpPipe, O_RDWR);
  }

  // open udp FIFO
  if (enable_udp == 1) {
	//udp_fd = open(udpPipe, O_WRONLY);
	udp_fd = open(udpPipe, O_RDWR);
  }
}
  
/* signal */
void sig_int(int sig)
{
  struct pcap_stat ps;
  int rvalue;

  /* --------- Statistics -----------*/
  rvalue = pcap_stats(pd, &ps);
  printf("-------------- Statistics ------------------\n");
  printf("Received: %d\n", ps.ps_recv);
  printf("Dropped : %d\n", ps.ps_drop);
  printf("(%d)Bye!!\n", sig);
  printf("--------------------------------------------\n");

#ifdef DEBUG_MEM
  printf("Mem alloc:%ld\n", mem_alloc);
  printf("Mem free:%ld\n", mem_free);
  printf("Mem alloc tcp:%ld\n", mem_alloc_tcp);
  printf("Mem alloc udp:%ld\n", mem_alloc_udp);
  printf("Mem free irc:%ld\n", mem_free_irc);
  printf("Fifo Count:ld\n", fifo_count);
#endif
  
  /* need thread join */
  /* since terminate abnormally */

  /* Join threads */
  pcap_close(pd);
  exit(0);
}

void usage(void)
{
  fprintf(stdout, " Usage: \n");
  fprintf(stdout, "      -i : interface name\n");
  fprintf(stdout, "      -T : disable TCP capture\n");
  fprintf(stdout, "      -U : disable UDP capture\n");
  fprintf(stdout, "      -c : number of packet count\n");
  fprintf(stdout, "      -h : usage\n");
}

int main(int argc, char *argv[])
{
  char ebuf[PCAP_ERRBUF_SIZE];
  int c, packetcnt;
  int snaplen = 1000; /* capture size of each packet */

  int result;
  signal(SIGINT, sig_int);

  if (argc < 2) {
	usage();
	exit(1);
  }

  /* Options */
  while( (c = getopt(argc, argv, "i:T:U:c:h")) != -1) {
	switch(c) {
	case 'i':
	  device = optarg;
	  break;
	case 'T':
	  enable_tcp = 0;
	  break;
	case 'U':
	  enable_udp = 0;
	  break;
	case 'c':
	  cflag = 1;
	  packetcnt = atoi(optarg);
	  if(packetcnt <= 0) {
		fprintf(stderr, "invalid number %s", optarg);
		fprintf(stderr, "parameter must be positive number");
		exit(1);
	  }
	  break;
	case 'h':
	  usage();
	  exit(1);
	}
  }

  /* init Pipe */
  init_pipe();

  /* open capture interface */
  pd = pcap_open_live(device, snaplen, PROMISCUOUS, 10000, ebuf);
  if (pd == NULL) {
	perror(ebuf);
	exit(-1);
  }

  /* main processing */
  if ( (result = pcap_loop(pd, packetcnt, analysis, 0))  < 0) {
	printf("result:%d\n", result);
	perror("pcap_loop error");
	exit(-1);
  }

  pcap_close(pd);

  exit(0);
}
